package org.jg.arkanoid;

import java.awt.Color;
import java.awt.event.KeyListener;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jg.arkanoid.bonuses.Guns;
import org.jg.arkanoid.controller.CarriageKeyListener;
import org.jg.arkanoid.controller.GunsKeyListener;
import org.jg.arkanoid.controller.KeyBoardListener;
import org.jg.arkanoid.engine.collider.CollisionDetector;
import org.jg.arkanoid.gameobjects.Ball;
import org.jg.arkanoid.gameobjects.Brick;
import org.jg.arkanoid.gameobjects.Carriage;
import org.jg.arkanoid.gameobjects.GameObject;
import org.jg.arkanoid.gameobjects.MovingObject;
import org.jg.arkanoid.ui.GamePanel;
import org.jg.arkanoid.util.Vector;

public class TheGame 
{
	private static Logger LOG = LogManager.getLogger("TheGame.class");
	
	private GamePanel gamePanel;
	private List<GameObject> gameObjects;

	private boolean running = false;
	private boolean paused = false;
	private int frameCount = 0;
	
	private KeyBoardListener keyListener;
	
	public TheGame() {
		gameObjects = new LinkedList<GameObject>();
		CollisionDetector.getInstance().setGameObjects(gameObjects);
	}

	public void setGamePanel(GamePanel gamePanel) {
		this.gamePanel = gamePanel;
		CollisionDetector.getInstance().setSpaceBounds(gamePanel.getWidth(), gamePanel.getHeight());
	}

	public void setKeyListener( KeyBoardListener keyListener )
	{
		this.keyListener = keyListener;
	}
	
	public void runGame() {
		createGameObjects();
		
		running = true;
		runGameLoop();
	}
	
	public void addGameObject(GameObject gameObject)
	{
		if(gameObject != null)
		{
			gameObjects.add(gameObject);
			gamePanel.add(gameObject.getPanel());
		}
	}
	
	public void addKeyListener(KeyListener listener)
	{
		if(keyListener != null)
		{
			keyListener.addKeyListener(listener);
		}
	}
	
	private void createGameObjects()
	{
		Ball ball = new Ball();
		ball.setPosition(new Vector(10, 10));
		ball.setHeight(20);
		ball.setWidth(20);
		ball.setColor(Color.RED);
		ball.setVelocity(new Vector(-1.1,-1.1));
		ball.redrawObject();
		
		addGameObject(ball);
		
		Ball ball2 = new Ball();
		ball2.setPosition(new Vector(30, 30));
		ball2.setHeight(20);
		ball2.setWidth(20);
		ball2.setColor(Color.GREEN);
		ball2.setVelocity(new Vector(-1.5,1.5));
		ball2.redrawObject();
		
		addGameObject(ball2);
		
		Ball ball3 = new Ball();
		ball3.setPosition(new Vector(60, 60));
		ball3.setHeight(20);
		ball3.setWidth(20);
		ball3.setColor(Color.BLUE);
		ball3.setVelocity(new Vector(2,-2));
		ball3.redrawObject();
		
		addGameObject(ball3);
		
		Ball ball4 = new Ball();
		ball4.setPosition(new Vector(90, 90));
		ball4.setHeight(20);
		ball4.setWidth(20);
		ball4.setColor(Color.YELLOW);
		ball4.setVelocity(new Vector(-2,-2));
		ball4.redrawObject();
		
		addGameObject(ball4);
		
		Ball ball5 = new Ball();
		ball5.setPosition(new Vector(200, 90));
		ball5.setHeight(20);
		ball5.setWidth(20);
		ball5.setColor(Color.MAGENTA);
		ball5.setVelocity(new Vector(1.8,1));
		ball5.redrawObject();
		
		addGameObject(ball5);
		
		Brick brick = new Brick(); 
		brick.setPosition(new Vector(300, 10));
		brick.setHeight(20);
		brick.setWidth(80);
		brick.setColor(Color.ORANGE);
		brick.redrawObject();
		
		addGameObject(brick);
		
//		Ball ball6 = new Ball();
//		ball6.setPosition(new Vector(500, 300));
//		ball6.setHeight(20);
//		ball6.setWidth(20);
//		ball6.setColor(Color.PINK);
//		ball6.setVelocity(new Vector(-1,0));
//		ball6.redrawObject();
//		
//		addGameObject(ball6);
		
		
		Carriage carriage = new Carriage();
		carriage.setPosition(new Vector(300, 300));
		carriage.setHeight(20);
		carriage.setWidth(80);
		carriage.setColor(Color.CYAN);
		carriage.redrawObject();
		carriage.setHorizontalVelocity(6);
		addKeyListener(new CarriageKeyListener(carriage));
		
		addGameObject(carriage);
		
		Guns guns = new Guns( carriage );
		guns.setHeight(12);
		guns.setWidth(8);
		guns.setColor(Color.DARK_GRAY);
		guns.redrawObject();
		addKeyListener(new GunsKeyListener(guns));
		
		addGameObject(guns);
	}
	
	private void runGameLoop() {
		Thread loop = new Thread() {
			public void run() {
				gameLoop();
			}
		};
		loop.start();
	}

	private void gameLoop() {
		// This value would probably be stored elsewhere.
		final long NANOSECONDS_IN_SECOND = 1000000000;
		
		final double GAME_HERTZ = 60.0;
		// Calculate how many ns each frame should take for our target
		// gamehertz.
		final double TIME_BETWEEN_UPDATES = NANOSECONDS_IN_SECOND / GAME_HERTZ;
		// At the very most we will update the game this many times before a new
		// render.
		// If you're worried about visual hitches more than perfect timing, set
		// this to 1.
		final int MAX_UPDATES_BEFORE_RENDER = 1;
		// We will need the last update time.
		double lastUpdateTime = System.nanoTime();
		// Store the last time we rendered.
		double lastRenderTime = System.nanoTime();

		// If we are able to get as high as this FPS, don't render again.
		final double TARGET_FPS = 60;
		final double TARGET_TIME_BETWEEN_RENDERS = NANOSECONDS_IN_SECOND / TARGET_FPS;

		// Simple way of finding FPS.
		double lastSecondTime = 0;
		
		while(running)
		{
			double now = System.nanoTime();
			int updateCount = 0;
			
			if (!paused) 
			{
				//Do as many game updates as we need to, potentially playing catchup.
	            while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
	            {
	               updateGame();
	               lastUpdateTime += TIME_BETWEEN_UPDATES;
	               updateCount++;
	            }
	            
				// If for some reason an update takes forever, we don't want to do an insane number of catchups.
				// If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
				if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) 
				{
					lastUpdateTime = now - TIME_BETWEEN_UPDATES;
				}
				
				lastRenderTime = now;
				drawField();
				frameCount++;
				
				//Update the frames we got.
	            if (now - lastSecondTime > NANOSECONDS_IN_SECOND)
	            {
	               LOG.info("fps: " + frameCount);
	               frameCount = 0;
	               lastSecondTime = now;
	            }
	         
	            //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
	            while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
	            {
	               Thread.yield();
	            
	               //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
	               //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
	               //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
	               int timeToSleep_ms = (int) (lastRenderTime + TARGET_TIME_BETWEEN_RENDERS - now)/1000000;
	               
	               try {Thread.sleep(timeToSleep_ms);} catch(Exception e) {} 
	            
	               now = System.nanoTime();
	            }
				
			}
		}
	}

	private void updateGame()
	{
		destroyMarkedObjects(gameObjects);
		
		CollisionDetector.getInstance().checkForCollisions();
		
		for(GameObject gameObject : gameObjects)
		{
			if( gameObject instanceof MovingObject )
			{
				MovingObject movingObject = (MovingObject)gameObject;
				movingObject.move();
			}
		}
	}
	
	private void destroyMarkedObjects(Collection<GameObject> gameObjects) 
	{
		Iterator<GameObject> iter = gameObjects.iterator();

		while (iter.hasNext()) {
			GameObject obj = iter.next();
			if (obj.isShouldBeDestoryed()) {
				iter.remove();
				gamePanel.remove(obj.getPanel());
			}
		}
	}
	
	private void drawField() {
		for (GameObject obj : gameObjects) {
			obj.redrawObject();
		}

		gamePanel.repaint();
	}
}
